﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace ScrumTable.UI.View.CustomControl
{
    public class VirtualizingMultiLinePanel : VirtualizingPanel
    {
        // Fields
        public static readonly DependencyProperty IsVirtualizingProperty = DependencyProperty.Register("IsVirtualizing", typeof (bool), typeof (VirtualizingMultiLinePanel), new FrameworkPropertyMetadata(true));
        public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation",typeof (Orientation),typeof (VirtualizingMultiLinePanel),new FrameworkPropertyMetadata(Orientation.Horizontal,FrameworkPropertyMetadataOptions.AffectsArrange |FrameworkPropertyMetadataOptions.AffectsMeasure,OnOrientationChanged),IsValidOrientation);
        public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(VirtualizingMultiLinePanel), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(VirtualizingMultiLinePanel.OnRowsChanged)), new ValidateValueCallback(VirtualizingMultiLinePanel.IsValidRows));
        public static readonly DependencyProperty VirtualizingBufferColumnsProperty =DependencyProperty.Register("VirtualizingBufferColumns", typeof (int), typeof (VirtualizingMultiLinePanel),new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.None));
        
        
        /// <summary>
        /// If an element in the collection gets "MOVED" (collection.move()) it must be deleted manuelly from the InternalChildren collection.
        /// Because of that, the animation goes crazy (looks bad). In this IDictionary the transformation are stored and they are used to reset the item after the move action.
        /// </summary>
        private IDictionary<object, Transform> StoredTransformationBeforMove = new Dictionary<object, Transform>();
        private Size _childSize;
        private Size _extentSize;
        private bool _isChildSizeReady;
        private IList _items;
        private bool _needMeasure;
        private Rect _originalViewport;
        private Rect _viewport;
        private bool _showContentCenter = true;

        public VirtualizingMultiLinePanel()
        {
            VerticalAlignment = VerticalAlignment.Top;
            HorizontalAlignment = HorizontalAlignment.Left;
            
        }


        private Size ChildSize
        {
            get
            {
                if (!_isChildSizeReady && (ItemsCount != 0))
                {
                    _isChildSizeReady = true;
                    _childSize = GetChildSize(GetChildAvailableSize(_extentSize));
                }
                return _childSize;
            }
        }

        private int Cols
        {
            get { return (int) Math.Ceiling(((ItemsCount)/((double) Rows))); }
        }

        protected override bool HasLogicalOrientation
        {
            get { return true; }
        }

        public bool IsVirtualizing
        {
            get { return (bool) base.GetValue(IsVirtualizingProperty); }
            set { base.SetValue(IsVirtualizingProperty, value); }
        }

        private IList Items
        {
            get
            {
                if (_items == null)
                {
                    ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
                    if ((itemsOwner != null) && !itemsOwner.IsGrouping)
                    {
                        _items = itemsOwner.Items;
                    }
                    else
                    {
                        GroupItem item;
                        if (WindowUtilities.TryGetVisualAncestor(this, out item))
                        {
                            var dataContext = item.DataContext as CollectionViewGroup;
                            if (dataContext != null)
                            {
                                _items = dataContext.Items;
                            }
                        }
                    }
                }
                return _items;
            }
        }

        private int ItemsCount
        {
            get
            {
                ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
                if (itemsOwner == null)
                {
                    return base.Children.Count;
                }
                if (itemsOwner.HasItems && (Items != null))
                {
                    return Items.Count;
                }
                return 0;
            }
        }

        
        public bool ShowContentCenter
        {
            get { return _showContentCenter; }
            set { _showContentCenter = value; }
        }

        protected override Orientation LogicalOrientation
        {
            get { return Orientation; }
        }

        public Orientation Orientation
        {
            get { return (Orientation) base.GetValue(OrientationProperty); }
            set { base.SetValue(OrientationProperty, value); }
        }

        public int Rows
        {
            get { return (int) base.GetValue(RowsProperty); }
            set { base.SetValue(RowsProperty, value); }
        }

        public int VirtualizingBufferColumns
        {
            get { return (int) base.GetValue(VirtualizingBufferColumnsProperty); }
            set { base.SetValue(VirtualizingBufferColumnsProperty, value); }
        }

        // Methods
        protected override Size ArrangeOverride(Size finalSize)
        {
           
            ScrollViewer viewer;
            UpdateExtentSize(finalSize);
            if (!WindowUtilities.TryGetVisualAncestor(this, out viewer))
            {
                _viewport = new Rect(new Size(double.PositiveInfinity, double.PositiveInfinity));
            }
            else
            {
                Point location = viewer.TranslatePoint(new Point(0.0, 0.0), this);
                _originalViewport = new Rect(location, new Size(viewer.ActualWidth, viewer.ActualHeight));
                _viewport = _originalViewport;
                _originalViewport.Offset(-viewer.HorizontalOffset, -viewer.VerticalOffset);
            }

            UpdateChildren(viewer.VerticalOffset, true);


            return _extentSize;
        }

        private void CleanUpChildren(int firstVisibleIndex, int lastVisibleIndex)
        {
            UIElementCollection internalChildren = base.InternalChildren;
            IItemContainerGenerator itemContainerGeneratorForPanel =
                base.ItemContainerGenerator.GetItemContainerGeneratorForPanel(this);
            for (int i = internalChildren.Count - 1; i >= 0; i--)
            {
                var position = new GeneratorPosition(i, 0);
                int num2 = itemContainerGeneratorForPanel.IndexFromGeneratorPosition(position);
                if ((num2 != 0) && ((num2 < firstVisibleIndex) || (num2 > lastVisibleIndex)))
                {
                    itemContainerGeneratorForPanel.Remove(position, 1);
                    base.RemoveInternalChildRange(i, 1);
                }
            }
        }
        
        private Size GetChildAvailableSize(Size availableSize)
        {
            
            if (Orientation == Orientation.Vertical)
            {
                return new Size(availableSize.Width/(Rows), double.PositiveInfinity);
            }
            return new Size(double.PositiveInfinity, availableSize.Height/(Rows));
        }

        private Rect GetChildRect(int index)
        {
            Size childSize = ChildSize;
            var num = (int) Math.Floor(((index)/((double) Rows)));
            int num2 = index%Rows;
            if (Orientation == Orientation.Horizontal)
            {
                return new Rect(childSize.Width*num, childSize.Height*num2, childSize.Width, childSize.Height);
            }
            return new Rect(childSize.Width*num2, childSize.Height*num, childSize.Width, childSize.Height);
        }


       

        private Size GetChildSize(Size availableSize)
        {
            UIElementCollection internalChildren = base.InternalChildren;
            IItemContainerGenerator itemContainerGenerator = base.ItemContainerGenerator;
            GeneratorPosition position = itemContainerGenerator.GeneratorPositionFromIndex(0);
            using (itemContainerGenerator.StartAt(position, GeneratorDirection.Forward, true))
            {
                bool flag;
                var child = itemContainerGenerator.GenerateNext(out flag) as UIElement;
                if (flag)
                {
                    base.InsertInternalChild(0, child);
                    itemContainerGenerator.PrepareItemContainer(child);
                }
                else
                {
                    child.InvalidateArrange();
                }
                child.Measure(availableSize);
                var size = new Size();
                if (double.IsInfinity(availableSize.Width))
                {
                    size.Width = child.DesiredSize.Width;
                }
                else
                {
                    size.Width = (availableSize.Width >= child.DesiredSize.Width)
                                     ? availableSize.Width
                                     : child.DesiredSize.Width;
                }
                if (double.IsInfinity(availableSize.Height))
                {
                    size.Height = child.DesiredSize.Height;
                }
                else
                {
                    size.Height = (availableSize.Height >= child.DesiredSize.Height)
                                      ? availableSize.Height
                                      : child.DesiredSize.Height;
                }
                return size;
            }
        }

        private Rect GetViewportWithBuffer()
        {
            Rect viewport = this._viewport;
            int virtualizingBufferColumns = VirtualizingBufferColumns;
            if (virtualizingBufferColumns > 0)
            {
                if (Orientation == Orientation.Horizontal)
                {
                    viewport.Inflate((ChildSize.Width*virtualizingBufferColumns), 0.0);
                    return viewport;
                }
                viewport.Inflate(0.0, (ChildSize.Height*virtualizingBufferColumns));
            }
            return viewport;
        }

        private void InvalidateChildSize()
        {
            _isChildSizeReady = false;
            _needMeasure = true;
        }

        internal static bool IsValidOrientation(object o)
        {
            var orientation = (Orientation) o;
            if (orientation != Orientation.Horizontal)
            {
                return (orientation == Orientation.Vertical);
            }
            return true;
        }

        internal static bool IsValidRows(object o)
        {
            var num = (int) o;
            return ((num > 0) && (num <= 0x7fffffff));
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            if (ItemsCount > 0)
            {
                _isChildSizeReady = true;
                _childSize = GetChildSize(GetChildAvailableSize(availableSize));
                if (Orientation == Orientation.Horizontal)
                {
                    if (!Equals(_extentSize.Height, ChildSize.Height*Rows))
                    {
                        _needMeasure = true;
                    }
                }
                else if (!Equals(_extentSize.Width, ChildSize.Width*Rows))
                {
                    _needMeasure = true;
                }
            }
            UpdateExtentSize(availableSize);
            return _extentSize;
        }

        public void OnAncestorScrollChanged(object sender, RoutedEventArgs args)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }
            var originalSource = args.OriginalSource as ScrollViewer;
            var args2 = args as ScrollChangedEventArgs;
            /*
            if( args2.VerticalChange + args2.HorizontalChange == 0)
            {
                return;
            }*/
            if ((originalSource != null) && (args2 != null))
            {
                if (((_originalViewport.Height != originalSource.ActualHeight) ||
                     (_originalViewport.Width != originalSource.ActualWidth)) ||
                    ((args2.ExtentHeightChange != 0.0) || (args2.ExtentWidthChange != 0.0)))
                {
                    Point location = originalSource.TranslatePoint(new Point(0.0, 0.0), this);
                    _originalViewport = new Rect(location,
                                                new Size(originalSource.ActualWidth, originalSource.ActualHeight));
                    _originalViewport.Offset(-args2.HorizontalOffset, -args2.VerticalOffset);
                }
                _viewport = _originalViewport;
                _viewport.Offset(args2.HorizontalOffset, args2.VerticalOffset);

                UpdateChildren(args2.VerticalOffset, false);
            }
        }

        protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
        {
            base.OnItemsChanged(sender, args);
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    break;
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Replace:
                    if (args.Position.Index >= 0)
                    {
                        if (StoredTransformationBeforMove.ContainsKey(((FrameworkElement)InternalChildren[args.Position.Index]).DataContext))
                        {
                            StoredTransformationBeforMove.Remove(((FrameworkElement)InternalChildren[args.Position.Index]).DataContext);
                        }
                        RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
                        _items = null;
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    if (args.OldPosition.Index >= 0)
                    {
                        StoredTransformationBeforMove[((FrameworkElement)InternalChildren[args.OldPosition.Index]).DataContext] = InternalChildren[args.OldPosition.Index].RenderTransform; 
                        RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
                    }
                    break;
            }
        }


        private static void OnOrientationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var panel = sender as VirtualizingMultiLinePanel;
            if (panel != null)
            {
                panel.InvalidateChildSize();
            }
        }

        private static void OnRowsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var panel = sender as VirtualizingMultiLinePanel;
            if (panel != null)
            {
                panel.InvalidateChildSize();
            }
        }

        private void UpdateChildren(double offsetY, bool animated)
        {
            UIElementCollection internalChildren = base.InternalChildren;
            _offset = double.NaN;
            if (base.ItemContainerGenerator != null)
            {
                var rect = new Rect(_extentSize);
                Rect viewportWithBuffer = GetViewportWithBuffer();
                viewportWithBuffer.Intersect(rect);
                int firstVisibleIndex = 0;
                int lastVisibleIndex = -1;
                if ((viewportWithBuffer.Height > 0.0) && (viewportWithBuffer.Width > 0.0))
                {
                    firstVisibleIndex = GetIndexInfo(viewportWithBuffer, out lastVisibleIndex);

                    IItemContainerGenerator itemContainerGeneratorForPanel = base.ItemContainerGenerator.GetItemContainerGeneratorForPanel(this);
                    GeneratorPosition position = itemContainerGeneratorForPanel.GeneratorPositionFromIndex(firstVisibleIndex);
                    int index = (position.Offset == 0) ? position.Index : (position.Index + 1);
                    using (itemContainerGeneratorForPanel.StartAt(position, GeneratorDirection.Forward, true))
                    {
                        int currentIndex = firstVisibleIndex;
                        while (currentIndex <= lastVisibleIndex)
                        {
                            bool flag;
                            var child = itemContainerGeneratorForPanel.GenerateNext(out flag) as UIElement;
                            if (child != null)
                            {
                                PreparingChildren(itemContainerGeneratorForPanel, child, flag, index, internalChildren);
                                ArrangeChildren(currentIndex, child);
                                Rect childRec = GetChildRect(currentIndex);
                                if (ShowContentCenter)
                                {
                                    CalcCenterOffSet(childRec);
                                }
                                AddUpdateAnimationToChildren(childRec, child, offsetY, animated);
                            }
                            currentIndex++;
                            index++;
                        }
                    }
                }
                _needMeasure = false;
                CleanUpChildren(firstVisibleIndex, lastVisibleIndex);
            }
        }

        

        private int GetIndexInfo(Rect viewportWithBuffer, out int lastVisibleIndex)
        {
            int firstVisbleIndex;
            if (Orientation == Orientation.Horizontal)
            {
                firstVisbleIndex = Math.Max(0, Rows*((int) (viewportWithBuffer.X/ChildSize.Width)));
                lastVisibleIndex = Math.Min((ItemsCount - 1),((Rows*((int) Math.Ceiling((viewportWithBuffer.Right/ChildSize.Width)))) -1));
            }
            else
            {
                firstVisbleIndex = Math.Max(0, Rows*((int) (viewportWithBuffer.Y/ChildSize.Height)));
                lastVisibleIndex = Math.Min((ItemsCount - 1),((Rows*((int) Math.Ceiling((viewportWithBuffer.Bottom/ChildSize.Height)))) -1));
            }
            return firstVisbleIndex;
        }

        private void PreparingChildren(IItemContainerGenerator itemContainerGeneratorForPanel, UIElement child, bool flag, int index, UIElementCollection internalChildren)
        {
            if (flag)
            {
                if (index >= internalChildren.Count)
                {
                    base.AddInternalChild(child);
                }
                else
                {
                    base.InsertInternalChild(index, child);
                }
                itemContainerGeneratorForPanel.PrepareItemContainer(child);
            }
            if (StoredTransformationBeforMove.ContainsKey(((FrameworkElement)child).DataContext))
            {
                child.RenderTransform = StoredTransformationBeforMove[((FrameworkElement)child).DataContext];
            }
        }

        private void ArrangeChildren(int currentIndex, UIElement child)
        {
            child.Measure(GetChildAvailableSize(_extentSize));
            if (currentIndex == 0)
            {
                if (!child.IsArrangeValid)
                {
                    child.Arrange(new Rect(new Point(0, 0), GetChildRect(currentIndex).Size));
                }
            }
            else
            {
                if ((_needMeasure || !child.IsMeasureValid) || !child.IsArrangeValid)
                {
                    child.Measure(GetChildAvailableSize(_extentSize));
                }
                child.Arrange(new Rect(new Point(0, 0), GetChildRect(currentIndex).Size));
            }
        }

        private double _offset;

        private void CalcCenterOffSet(Rect childRec)
        {
            if(double.IsNaN(_offset))
            {
                if (Orientation == Orientation.Horizontal)
                {
                    int rows = (int)Math.Floor(_viewport.Height/ childRec.Height);
                    _offset = ((_viewport.Height - 38 /*ScrollBar Width*/- (childRec.Height) * rows)) / 2;
                }
                else
                {
                    int rows =  (int)Math.Floor(_viewport.Width / childRec.Width);
                    _offset = ((_viewport.Width - 38 /*ScrollBar Width*/- (childRec.Width) * rows)) / 2;
                }
            }
        }

        private void AddUpdateAnimationToChildren(Rect childRec, UIElement child, double offsetY, bool animated)
        {
            
            var trans = child.RenderTransform as TranslateTransform;

            if (trans == null)
            {
                child.RenderTransformOrigin = new Point(0, 0);
                trans = new TranslateTransform(0, offsetY);
                child.RenderTransform = trans;
            }
            if (_offset > 0)
            {
                if (Orientation == Orientation.Horizontal) { childRec.Y += _offset; }
                if (Orientation == Orientation.Vertical) { childRec.X += _offset; }
            }
            if (childRec.Y != trans.Y || childRec.X != trans.X)
            {
                if (animated)
                {
                    TimeSpan _animationLength = TimeSpan.FromMilliseconds(400);
                    trans.BeginAnimation(TranslateTransform.XProperty,new DoubleAnimation(childRec.X, _animationLength),HandoffBehavior.Compose);
                    trans.BeginAnimation(TranslateTransform.YProperty,new DoubleAnimation(childRec.Y, _animationLength),HandoffBehavior.Compose);
                }
                else
                {
                    trans.X = childRec.X;
                    trans.Y = childRec.Y;
                }
            }
        }


        private void UpdateExtentSize(Size availableSize)
        {
            if (Orientation == Orientation.Vertical)
            {
                if (double.IsInfinity(availableSize.Width) || (availableSize.Width == 0.0))
                {
                    availableSize.Width = _childSize.Width*Rows;
                }
                _extentSize = new Size(availableSize.Width, _childSize.Height*Cols);
                if ((_extentSize.Height == 0.0) && !double.IsInfinity(availableSize.Height))
                {
                    _extentSize.Height = availableSize.Height;
                }
            }
            else
            {
                if (double.IsInfinity(availableSize.Height) || (availableSize.Height == 0.0))
                {
                    availableSize.Height = _childSize.Height*Rows;
                }
                _extentSize = new Size(_childSize.Width*Cols, availableSize.Height);
                if ((_extentSize.Width == 0.0) && !double.IsInfinity(availableSize.Width))
                {
                    _extentSize.Width = availableSize.Width;
                }
            }
        }
    }


    internal static class WindowUtilities
    {
        // Methods
        public static void EnsureRenderTransform(UIElement element)
        {
            if (element != null)
            {
                var group = new TransformGroup();
                group.Children.Add(new ScaleTransform(1.0, 1.0));
                group.Children.Add(new SkewTransform(0.0, 0.0));
                group.Children.Add(new RotateTransform(0.0));
                group.Children.Add(new TranslateTransform(0.0, 0.0));
                element.RenderTransform = group;
                element.RenderTransformOrigin = new Point(0.5, 0.5);
            }
        }

        public static T GetVisualAncestorByLogicalTree<T>(DependencyObject descendent) where T : class
        {
            DependencyObject current = descendent;
            T local = default(T);
            while ((current != null) && ((local = current as T) == null))
            {
                current = LogicalTreeHelper.GetParent(current);
            }
            return local;
        }


        internal static Window GetWindow(DependencyObject childObject)
        {
            Window window = Window.GetWindow(childObject);
            DependencyObject reference = childObject;
            while ((window == null) && (reference != null))
            {
                reference = VisualTreeHelper.GetParent(reference);
                window = reference as Window;
            }
            return window;
        }
       
        public static bool TryGetVisualAncestor<T>(DependencyObject descendent, out T ancestor) where T : class
        {
            DependencyObject reference = descendent;
            ancestor = default(T);
            Label_0012:
            if (reference != null)
            {
                if ((ancestor = reference as T) == null)
                {
                    reference = VisualTreeHelper.GetParent(reference);
                    goto Label_0012;
                }
            }
            return ((ancestor) != null);
        }
    }
}